#!/sbin/python
import os 
import mysql.connector
import platform
import sys
import random
from time import strftime, localtime,sleep

# Mantengo una semana de historia en los snapshots.
DAYS_TO_CLEAN = 7

class DictDiffer(object):
    """
    Calculate the difference between two dictionaries as:
    (1) items added
    (2) items removed
    (3) keys same in both but changed values
    (4) keys same in both and unchanged values
    """
    def __init__(self, current_dict, past_dict):
        self.current_dict, self.past_dict = current_dict, past_dict
        self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys())
        self.intersect = self.set_current.intersection(self.set_past)
    def added(self):
        return self.set_current - self.intersect 
    def removed(self):
        return self.set_past - self.intersect 
    def changed(self):
        return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o])
    def unchanged(self):
        return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o])
    
class Config(object):
    """Configure me so examples work
    
    Use me like this:
    
        mysql.connector.Connect(**Config.dbinfo())
    """
    
    HOST = ''
    DATABASE = ''
    USER = ''
    PASSWORD = ''
    PORT = 3306
    
    CHARSET = 'utf8'
    UNICODE = True
    WARNINGS = True
    BUFFERED = True
    @classmethod
    def dbinfo(cls):
        return {
            'host': cls.HOST,
            'port': cls.PORT,
            'database': cls.DATABASE,
            'user': cls.USER,
            'password': cls.PASSWORD,
            'charset': cls.CHARSET,
            'use_unicode': cls.UNICODE,
            'get_warnings': cls.WARNINGS,
            'buffered': cls.BUFFERED
            }
        

def raw_string(s):
    if isinstance(s, str):
        s = s.encode('string-escape')
    elif isinstance(s, unicode):
        s = s.encode('unicode-escape')
    return s

def get_sha256(input_file_name):
    """Calcular el hash SHA256 de un objeto arbitrario.
    """
    from datetime import datetime
    starttime = datetime.now()
    # START: Actual checksum calculation
    from hashlib import sha256
    chunk_size = 1048576  # 1024 B * 1024 B = 1048576 B = 1 MB 
    file_sha256_checksum = sha256()
    try:
        f = open(input_file_name, "rb")
        byte = f.read(chunk_size)
        previous_byte = byte
        byte_size = len(byte)
        file_read_iterations = 1
        while byte:
            file_sha256_checksum.update(byte)
            previous_byte = byte
            byte = f.read(chunk_size)
            byte_size += len(byte)
            file_read_iterations += 1
    except IOError:
        print ('NO PUEDO ABRIR EL ARCHIVO : %s' % (input_file_name))
        # exit()
        return
    except:
        raise
    # END: Actual checksum calculation
    return file_sha256_checksum

def get_manifest(input_file_name):
    import zipfile
    z = zipfile.ZipFile(input_file_name, "r")
    try:
        data = z.read('META-INF/MANIFEST.MF')
        return data
    except:
        return 'Sin manifiesto.\n'

          
def clean_up():
    db = mysql.connector.Connect(**Config.dbinfo())
    cursor = db.cursor() 
    now = strftime("%Y-%m-%d %H:%M:%S", localtime())
    stmt_insert = r"DELETE FROM pysnapshot WHERE DATEDIFF('%s', timestamp) >= %d" % (now, DAYS_TO_CLEAN)
    cursor.execute(stmt_insert)
    print "Elimino : %d registros antiguos" % (cursor.rowcount) 
    db.commit()
    cursor.close()
    db.close()
    
def get_last_timestamp(server, directory):
    db = mysql.connector.Connect(**Config.dbinfo())
    cursor = db.cursor()     
    # Obtengo la ultima fecha de un snapshot sobre este directorio
    stmt_vrfy = r"SELECT timestamp FROM pysnapshot WHERE server = '%s' AND directory = '%s' order by timestamp desc limit 1" % (server, raw_string(directory))
    cursor.execute(stmt_vrfy)
    if (cursor.rowcount):
        return cursor.fetchone()[0]

    cursor.close()
    db.close() 

def get_number_of_files_in_snapshot(server, directory, timestamp):
    db = mysql.connector.Connect(**Config.dbinfo())
    cursor = db.cursor()     
    # Obtengo la ultima fecha de un snapshot sobre este directorio
    stmt_vrfy = r"SELECT count(timestamp) FROM pysnapshot WHERE server = '%s' AND directory = '%s' and timestamp = '%s'" % (server, raw_string(directory), timestamp)
    cursor.execute(stmt_vrfy)
    if (cursor.rowcount):
        return cursor.fetchone()[0]
    cursor.close()
    db.close() 
    
def dir_walk(directory):
    print "Iniciando chequeo en " + directory
    timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime())
    hostname = platform.uname()[1]
    directory = directory
    db = mysql.connector.Connect(**Config.dbinfo())
    cursor = db.cursor() 
    archivos = 0
    last_timestamp = get_last_timestamp(hostname, directory)

    first_run = False
    if (last_timestamp is not None):
        print "Ultimo snapshot : %s" % (last_timestamp)
        files_in_last_snapshot = get_number_of_files_in_snapshot(hostname, directory, last_timestamp) 
        print "Cantidad de Archivos en ultimo snapshot: %d" % (files_in_last_snapshot)
    else:
        print "No hay snapshot!"
        first_run = True
                  
    stmt_vrfy = r"SELECT path, hash FROM pysnapshot WHERE server = '%s' AND directory = '%s' and timestamp = '%s'" % (hostname, raw_string(directory), last_timestamp)
    cursor.execute(stmt_vrfy)
    # Todos los archivos y hashes en la ultima corrida
    files_in_last_snapshot = {}
    hashes_in_last_snapshot = {}
    if (cursor.rowcount):
        for row in cursor.fetchall():
            files_in_last_snapshot.update ({row[0]: row[1]})
            hashes_in_last_snapshot.update ({row[1]: row[0]})
            
    files_in_this_snapshot = {}
    hashes_in_this_snapshot = {}
    for root, dirs, files in os.walk(directory):
        
        # Elimino directorios de naturaleza transitoria.
        if 'tmp' in dirs: 
            dirs.remove('tmp')
        if 'temp' in dirs: 
            dirs.remove('temp')
        # Elimino directorio de logs.
        if 'log' in dirs:
            dirs.remove('log')
        # Elimino work y data.    
        if 'work' in dirs:
            dirs.remove('work')
        if 'data' in dirs:
            dirs.remove('data')
            
        # Elimino directorios de backups conocidos.
        if 'backup' in dirs:
            dirs.remove('backup')
        if 'backups' in dirs:
            dirs.remove('backups')
        if 'Backup' in dirs:
            dirs.remove('Backup')
            
        # Elimino carpetas varias
        # Carpeta de Jira
        if 'index' in dirs:
            dirs.remove('index')
        # Carpeta de Jira
        if 'idx2' in dirs:
            dirs.remove('idx2')
            
        # Carpeta de reportes?
        if 'reportes' in dirs:
            dirs.remove('reportes')
            
        # Carpeta de  OSPragma
        if 'files' in dirs:
            dirs.remove('files')

    
        for names in files:   
            # manifest = ''
            # if (filepath.endswith(".jar") or 
            #    filepath.endswith(".war")  or 
            #    filepath.endswith(".ear")):
            #    manifest =  get_manifest(filepat
            filepath = os.path.join(root, names)
            sys.stdout.write("\r                                                                      ")
            sys.stdout.write("\rIndexando %s" % names)
            sys.stdout.flush()
            hash = get_sha256(filepath).hexdigest()
            
            files_in_this_snapshot.update ({filepath:hash})
            hashes_in_this_snapshot.update ({hash:filepath})
            stmt_insert = r"INSERT INTO pysnapshot (timestamp, server, directory, path, hash) VALUES ('%s','%s', '%s', '%s', '%s')" % (timestamp, hostname, raw_string(directory), raw_string(filepath), hash)
            archivos += 1
            cursor.execute(stmt_insert)
            
    
    db.commit()
    sys.stdout.write("\n")
    
    d = DictDiffer(files_in_this_snapshot, files_in_last_snapshot)
    if not first_run:            
        print "Archivos creados : %s" % (len(d.added())) 
        for row in d.added():
            log = "%s - SHA256 = %s" % (row, files_in_this_snapshot[row])
            print log
            stmt_insert = r"INSERT INTO log_cambios (server,run_time,log) VALUES ('%s', '%s', 'Archivo(s) creado(s): %s')" % (hostname, timestamp, log)
            cursor.execute(stmt_insert)
            # #print files_in_last_snapshot[row]
        print "Archivos Eliminados : %s" % (len(d.removed()))
        for row in d.removed():
            log = "%s - SHA256 = %s" % (row, files_in_last_snapshot[row])
            print log
            stmt_insert = r"INSERT INTO log_cambios (server,run_time,log) VALUES ('%s', '%s', 'Archivo(s) creado(s): %s')" % (hostname, timestamp, log)
            cursor.execute(stmt_insert)
                   
        d = DictDiffer(hashes_in_this_snapshot, hashes_in_last_snapshot)            
        print "Archivos Modificados : %s" % (len(d.added())) 
        for row in d.added():
            log = "%s - SHA256 = %s" % (hashes_in_this_snapshot[row], row)
            print log
            stmt_insert = r"INSERT INTO log_cambios (server,run_time,log) VALUES ('%s', '%s', 'Archivo modificado: %s')" % (hostname, timestamp, log)
            cursor.execute(stmt_insert)
        
    else:
        print "Primera corrida de snapshot finalizada."
        stmt_insert = stmt_insert = r"INSERT INTO log_cambios (server,run_time,log) VALUES ('%s', '%s', 'Indexacion inicial : %s')" % (hostname, timestamp, raw_string(directory))
        cursor.execute(stmt_insert)
        
    print "Se procesaron %d archivos." % (archivos)
    db.commit()        
    cursor.close()
    db.close()
   
def print_header():
    print "Checksum de Archivos - El tripwire del hombre pobre"
    print "Administracion de Versiones"


if __name__ == "__main__":
    from optparse import OptionParser
    parser = OptionParser(usage="%prog [options] filename",
                          version="%prog 1.0")
    parser.add_option("-d", "--directory",
                      dest="directory",
                      help="Directorio base.")
    
    (options, args) = parser.parse_args()
    
    print options.directory
   
    if (options.directory):
        print_header()
        wait_time = int(random.uniform(300,900))
        print "Se retrasa el inicio %d segundos..." % (wait_time) 
        sleep(wait_time)
        for i in range(1,5):
            try:
                clean_up()
                dir_walk(options.directory)
                break
            except:
                # "Error! Espero y reintento"
                # Espero 45 Minutos
                sleep(2700)
                pass
                
    else:
        print_header()
        parser.error("Faltan opciones.")        
